/* ===================

AX2E (AXESCON XACML 2.0 Engine) is the Java authorization engine, which implements OASIS XACML 2.0 standard.
Copyright (C) 2007 AXESCON LLC

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA


Contact: AXESCON LLC - info{at}axescon.com

 =================== */
/*
* @(#)CTResponseComparator.java
*
* Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*   1. Redistribution of source code must retain the above copyright notice,
*      this list of conditions and the following disclaimer.
*
*   2. Redistribution in binary form must reproduce the above copyright
*      notice, this list of conditions and the following disclaimer in the
*      documentation and/or other materials provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
* AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
* AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for use in
* the design, construction, operation or maintenance of any nuclear facility.
*/

package axescon.xacml.test.conformance2_0;


import axescon.xacml.model.ctx.*;
import axescon.xacml.model.policy.Obligation;
import axescon.xacml.model.policy.AttributeAssignment;

import java.util.*;


/**
 * Responsibility: compare two responses.
 * This utility is used to compare XACML request evaluation response with
 * expected response. It was written specifically to run Conformance Tests.
 *
 * @author argyn
 *
 */
public class CTResponseComparator {

    /**
     * Returns whether two XACML Responses are equivalent.
     *
     * @param response an XACML Response
     * @param expectedResponse an XACML Response
     * @return true if the responses are equivalent, false otherwise
     */
    public static boolean equals(Response response,
                                 Response expectedResponse) {
        Result[] results = response.getResultArray();
        Result[] expectedResults = expectedResponse.getResultArray();

        if (results.length != expectedResults.length)
            return false;

        // matching results in the second set
        List matchedInExpectedResults = new ArrayList(); //Arrays.asList(expectedResults);

        // match each result in first response, to all results in the second response
        for (int i=0; i<results.length; i++) {
            Result result = results[i];
            boolean matched = false;

            // go through the second list, and see if there's a matching Result
            for (int j=0; j<expectedResults.length;j++) {
                Result expectedResult = expectedResults[j];
                if(matchedInExpectedResults.contains(expectedResult))continue; // already matched before

                if ((result.getDecision() == expectedResult.getDecision()) &&
                        (equalsResource(result.getResourceId(), expectedResult.getResourceId())) &&
                        equals(result.getStatus(), expectedResult.getStatus()) &&
                        equals(result.getObligationList(),
                                expectedResult.getObligationList())) {
                    matched = true;
                    break;
                }
            }


            if (matched)
                matchedInExpectedResults.add(expectedResults); // if there was a match, add to matched list
            else
                return false;
        }

        return true;
    }

    /**
     * compares String values, handling nulls.
     * @return true if Strings are equal or both null.
     */
    private static boolean equalsResource(String resourceId, String expectedResourceId) {
        if (resourceId == null)
            return (expectedResourceId == null);

        if (expectedResourceId == null) // temp fix, while CT doesn't have resource ids in expected responses
            return true;

        return resourceId.equals(expectedResourceId);
    }

    /**
     * Compares two XACML Status elements to see if they're equivalent.
     *
     * @param status1 status data
     * @param status2 status data
     * @return true if the two sets of status data are equivalent, false
     *         otherwise
     *
     */
    public static boolean equals(Status status1, Status status2) {
        StatusCode sc1 = status1.getStatusCode();
        StatusCode sc2 = status2.getStatusCode();

        return sc1.equals(sc2);
    }

    /**
     * Compares two lists of obligations
     * @param obligations
     * @param expectedObligations
     * @return true if the obligations are matching
     *
     * this code is ported from SunXACML sample code
     */
    public static boolean equals(List obligations, List expectedObligations) {
        if(obligations ==null)return expectedObligations ==null;
        if(expectedObligations ==null)return false;
        if (obligations.size() != expectedObligations.size())
            return false;

        // get an iterator for the first set of Obligations
        Iterator it = obligations.iterator();

        // setup a temporary Set for the second set of Obligations, so we can
        // remove the matching the Obligation at each step
        HashSet expectedSet = new HashSet(expectedObligations);

        // consider each Obligation in the first set, and try to find an
        // equivalent one in the second set
        while (it.hasNext()) {
            Obligation oblig = (Obligation) (it.next());
            Iterator expectedSetIt = expectedSet.iterator();
            boolean matched = false;

            // go through the second set, and see if there's a matching
            // Obligation
            while (expectedSetIt.hasNext() && (! matched)) {
                Obligation expectedOblig = (Obligation) (expectedSetIt.next());

                // to be equivalent, they need to have the same identifier
                // and the same fulfillOn setting
                if ((oblig.getObligationId().equals(expectedOblig.getObligationId())) &&
                        (oblig.getFulfillOn() == expectedOblig.getFulfillOn())) {

                    AttributeAssignment[] assignments = oblig.getAttributeAssignmentArray();
                    AttributeAssignment[] expectedAssignments = expectedOblig.getAttributeAssignmentArray();

                    if (assignments == null && expectedAssignments == null) matched = true;

                    else if (assignments == null || expectedAssignments == null) return false;
                    else if (assignments.length == expectedAssignments.length) {
                        boolean assignmentsMatch = true;

                        for (int j=0; j<assignments.length && assignmentsMatch;j++) {
                            AttributeAssignment aa =  assignments[j];
                            AttributeAssignment expectedAa = expectedAssignments[j];

                            if ((! aa.getAttributeId().equals(expectedAa.getAttributeId())) ||
                                    (! aa.getDataType().equals(expectedAa.getDataType())))
                                assignmentsMatch = false;
                        }

                        matched = assignmentsMatch;
                    }
                }
            }

            // if there was a match, remove it from the set...otherwise these
            // Obligations aren't equivalent
            if (matched)
                expectedSetIt.remove();
            else
                return false;
        }

        // if we got here then the Obligations are equivalent
        return true;
    }

}
